#include "emitterutils.h"
#include "exp.h"
#include "indentation.h"
#include "yaml-cpp/binary.h"
#include "yaml-cpp/exceptions.h"
#include "stringsource.h"
#include <sstream>
#include <iomanip>

namespace YAML
{
	namespace Utils
	{
		namespace {
			enum { REPLACEMENT_CHARACTER = 0xFFFD };

			bool IsAnchorChar(int ch) { // test for ns-anchor-char
				switch (ch) {
				case ',': case '[': case ']': case '{': case '}': // c-flow-indicator
				case ' ': case '\t': // s-white
				case 0xFEFF: // c-byte-order-mark
				case 0xA: case 0xD: // b-char
					return false;
				case 0x85:
					return true;
				}

				if (ch < 0x20)
					return false;

				if (ch < 0x7E)
					return true;

				if (ch < 0xA0)
					return false;
				if (ch >= 0xD800 && ch <= 0xDFFF)
					return false;
				if ((ch & 0xFFFE) == 0xFFFE)
					return false;
				if ((ch >= 0xFDD0) && (ch <= 0xFDEF))
					return false;
				if (ch > 0x10FFFF)
					return false;

				return true;
			}

			int Utf8BytesIndicated(char ch) {
				int byteVal = static_cast<unsigned char>(ch);
				switch (byteVal >> 4) {
				case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
					return 1;
				case 12: case 13:
					return 2;
				case 14:
					return 3;
				case 15:
					return 4;
				default:
					return -1;
				}
			}

			bool IsTrailingByte(char ch) {
				return (ch & 0xC0) == 0x80;
			}

			bool GetNextCodePointAndAdvance(int& codePoint, std::string::const_iterator& first, std::string::const_iterator last) {
				if (first == last)
					return false;

				int nBytes = Utf8BytesIndicated(*first);
				if (nBytes < 1) {
					// Bad lead byte
					++first;
					codePoint = REPLACEMENT_CHARACTER;
					return true;
				}

				if (nBytes == 1) {
					codePoint = *first++;
					return true;
				}

				// Gather bits from trailing bytes
				codePoint = static_cast<unsigned char>(*first) & ~(0xFF << (7 - nBytes));
				++first;
				--nBytes;
				for (; nBytes > 0; ++first, --nBytes) {
					if ((first == last) || !IsTrailingByte(*first)) {
						codePoint = REPLACEMENT_CHARACTER;
						break;
					}
					codePoint <<= 6;
					codePoint |= *first & 0x3F;
				}

				// Check for illegal code points
				if (codePoint > 0x10FFFF)
					codePoint = REPLACEMENT_CHARACTER;
				else if (codePoint >= 0xD800 && codePoint <= 0xDFFF)
					codePoint = REPLACEMENT_CHARACTER;
				else if ((codePoint & 0xFFFE) == 0xFFFE)
					codePoint = REPLACEMENT_CHARACTER;
				else if (codePoint >= 0xFDD0 && codePoint <= 0xFDEF)
					codePoint = REPLACEMENT_CHARACTER;
				return true;
			}

			void WriteCodePoint(ostream_wrapper& out, int codePoint) {
				if (codePoint < 0 || codePoint > 0x10FFFF) {
					codePoint = REPLACEMENT_CHARACTER;
				}
				if (codePoint < 0x7F) {
					out << static_cast<char>(codePoint);
				}
				else if (codePoint < 0x7FF) {
					out << static_cast<char>(0xC0 | (codePoint >> 6))
						<< static_cast<char>(0x80 | (codePoint & 0x3F));
				}
				else if (codePoint < 0xFFFF) {
					out << static_cast<char>(0xE0 | (codePoint >> 12))
						<< static_cast<char>(0x80 | ((codePoint >> 6) & 0x3F))
						<< static_cast<char>(0x80 | (codePoint & 0x3F));
				}
				else {
					out << static_cast<char>(0xF0 | (codePoint >> 18))
						<< static_cast<char>(0x80 | ((codePoint >> 12) & 0x3F))
						<< static_cast<char>(0x80 | ((codePoint >> 6) & 0x3F))
						<< static_cast<char>(0x80 | (codePoint & 0x3F));
				}
			}

			bool IsValidPlainScalar(const std::string& str, FlowType::value flowType, bool allowOnlyAscii) {
				if (str.empty())
					return false;

				// first check the start
				const RegEx& start = (flowType == FlowType::Flow ? Exp::PlainScalarInFlow() : Exp::PlainScalar());
				if (!start.Matches(str))
					return false;

				// and check the end for plain whitespace (which can't be faithfully kept in a plain scalar)
				if (!str.empty() && *str.rbegin() == ' ')
					return false;

				// then check until something is disallowed
				const RegEx& disallowed = (flowType == FlowType::Flow ? Exp::EndScalarInFlow() : Exp::EndScalar())
					|| (Exp::BlankOrBreak() + Exp::Comment())
					|| Exp::NotPrintable()
					|| Exp::Utf8_ByteOrderMark()
					|| Exp::Break()
					|| Exp::Tab();
				StringCharSource buffer(str.c_str(), str.size());
				while (buffer) {
					if (disallowed.Matches(buffer))
						return false;
					if (allowOnlyAscii && (0x80 <= static_cast<unsigned char>(buffer[0])))
						return false;
					++buffer;
				}

				return true;
			}

			bool IsValidSingleQuotedScalar(const std::string& str, bool escapeNonAscii)
			{
				// TODO: check for non-printable characters?
				for (std::size_t i = 0; i < str.size(); i++) {
					if (escapeNonAscii && (0x80 <= static_cast<unsigned char>(str[i])))
						return false;
					if (str[i] == '\n')
						return false;
				}
				return true;
			}

			bool IsValidLiteralScalar(const std::string& str, FlowType::value flowType, bool escapeNonAscii)
			{
				if (flowType == FlowType::Flow)
					return false;

				// TODO: check for non-printable characters?
				for (std::size_t i = 0; i < str.size(); i++) {
					if (escapeNonAscii && (0x80 <= static_cast<unsigned char>(str[i])))
						return false;
				}
				return true;
			}

			void WriteDoubleQuoteEscapeSequence(ostream_wrapper& out, int codePoint) {
				static const char hexDigits[] = "0123456789abcdef";

				out << "\\";
				int digits = 8;
				if (codePoint < 0xFF) {
					out << "x";
					digits = 2;
				}
				else if (codePoint < 0xFFFF) {
					out << "u";
					digits = 4;
				}
				else {
					out << "U";
					digits = 8;
				}

				// Write digits into the escape sequence
				for (; digits > 0; --digits)
					out << hexDigits[(codePoint >> (4 * (digits - 1))) & 0xF];
			}

			bool WriteAliasName(ostream_wrapper& out, const std::string& str) {
				int codePoint;
				for (std::string::const_iterator i = str.begin();
				GetNextCodePointAndAdvance(codePoint, i, str.end());
					)
				{
					if (!IsAnchorChar(codePoint))
						return false;

					WriteCodePoint(out, codePoint);
				}
				return true;
			}
		}

		StringFormat::value ComputeStringFormat(const std::string& str, EMITTER_MANIP strFormat, FlowType::value flowType, bool escapeNonAscii)
		{
			switch (strFormat) {
			case Auto:
				if (IsValidPlainScalar(str, flowType, escapeNonAscii))
					return StringFormat::Plain;
				return StringFormat::DoubleQuoted;
			case SingleQuoted:
				if (IsValidSingleQuotedScalar(str, escapeNonAscii))
					return StringFormat::SingleQuoted;
				return StringFormat::DoubleQuoted;
			case DoubleQuoted:
				return StringFormat::DoubleQuoted;
			case Literal:
				if (IsValidLiteralScalar(str, flowType, escapeNonAscii))
					return StringFormat::Literal;
				return StringFormat::DoubleQuoted;
			default:
				break;
			}

			return StringFormat::DoubleQuoted;
		}

		bool WriteSingleQuotedString(ostream_wrapper& out, const std::string& str)
		{
			out << "'";
			int codePoint;
			for (std::string::const_iterator i = str.begin();
			GetNextCodePointAndAdvance(codePoint, i, str.end());
				)
			{
				if (codePoint == '\n')
					return false;  // We can't handle a new line and the attendant indentation yet

				if (codePoint == '\'')
					out << "''";
				else
					WriteCodePoint(out, codePoint);
			}
			out << "'";
			return true;
		}

		bool WriteDoubleQuotedString(ostream_wrapper& out, const std::string& str, bool escapeNonAscii)
		{
			out << "\"";
			int codePoint;
			for (std::string::const_iterator i = str.begin();
			GetNextCodePointAndAdvance(codePoint, i, str.end());
				)
			{
				switch (codePoint) {
				case '\"': out << "\\\""; break;
				case '\\': out << "\\\\"; break;
				case '\n': out << "\\n"; break;
				case '\t': out << "\\t"; break;
				case '\r': out << "\\r"; break;
				case '\b': out << "\\b"; break;
				default:
					if (codePoint < 0x20 || (codePoint >= 0x80 && codePoint <= 0xA0)) // Control characters and non-breaking space
						WriteDoubleQuoteEscapeSequence(out, codePoint);
					else if (codePoint == 0xFEFF) // Byte order marks (ZWNS) should be escaped (YAML 1.2, sec. 5.2)
						WriteDoubleQuoteEscapeSequence(out, codePoint);
					else if (escapeNonAscii && codePoint > 0x7E)
						WriteDoubleQuoteEscapeSequence(out, codePoint);
					else
						WriteCodePoint(out, codePoint);
				}
			}
			out << "\"";
			return true;
		}

		bool WriteLiteralString(ostream_wrapper& out, const std::string& str, int indent)
		{
			out << "|\n";
			out << IndentTo(indent);
			int codePoint;
			for (std::string::const_iterator i = str.begin();
			GetNextCodePointAndAdvance(codePoint, i, str.end());
				)
			{
				if (codePoint == '\n')
					out << "\n" << IndentTo(indent);
				else
					WriteCodePoint(out, codePoint);
			}
			return true;
		}

		bool WriteChar(ostream_wrapper& out, char ch)
		{
			if (('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z'))
				out << ch;
			else if ((0x20 <= ch && ch <= 0x7e) || ch == ' ')
				out << "\"" << ch << "\"";
			else if (ch == '\t')
				out << "\"\\t\"";
			else if (ch == '\n')
				out << "\"\\n\"";
			else if (ch == '\b')
				out << "\"\\b\"";
			else {
				out << "\"";
				WriteDoubleQuoteEscapeSequence(out, ch);
				out << "\"";
			}
			return true;
		}

		bool WriteComment(ostream_wrapper& out, const std::string& str, int postCommentIndent)
		{
			const unsigned curIndent = static_cast<unsigned int> (out.col());
			out << "#" << Indentation(postCommentIndent);
			out.set_comment();
			int codePoint;
			for (std::string::const_iterator i = str.begin();
			GetNextCodePointAndAdvance(codePoint, i, str.end());
				)
			{
				if (codePoint == '\n') {
					out << "\n" << IndentTo(curIndent) << "#" << Indentation(postCommentIndent);
					out.set_comment();
				}
				else {
					WriteCodePoint(out, codePoint);
				}
			}
			return true;
		}

		bool WriteAlias(ostream_wrapper& out, const std::string& str)
		{
			out << "*";
			return WriteAliasName(out, str);
		}

		bool WriteAnchor(ostream_wrapper& out, const std::string& str)
		{
			out << "&";
			return WriteAliasName(out, str);
		}

		bool WriteTag(ostream_wrapper& out, const std::string& str, bool verbatim)
		{
			out << (verbatim ? "!<" : "!");
			StringCharSource buffer(str.c_str(), str.size());
			const RegEx& reValid = verbatim ? Exp::URI() : Exp::Tag();
			while (buffer) {
				int n = reValid.Match(buffer);
				if (n <= 0)
					return false;

				while (--n >= 0) {
					out << buffer[0];
					++buffer;
				}
			}
			if (verbatim)
				out << ">";
			return true;
		}

		bool WriteTagWithPrefix(ostream_wrapper& out, const std::string& prefix, const std::string& tag)
		{
			out << "!";
			StringCharSource prefixBuffer(prefix.c_str(), prefix.size());
			while (prefixBuffer) {
				int n = Exp::URI().Match(prefixBuffer);
				if (n <= 0)
					return false;

				while (--n >= 0) {
					out << prefixBuffer[0];
					++prefixBuffer;
				}
			}

			out << "!";
			StringCharSource tagBuffer(tag.c_str(), tag.size());
			while (tagBuffer) {
				int n = Exp::Tag().Match(tagBuffer);
				if (n <= 0)
					return false;

				while (--n >= 0) {
					out << tagBuffer[0];
					++tagBuffer;
				}
			}
			return true;
		}

		bool WriteBinary(ostream_wrapper& out, const Binary& binary)
		{
			WriteDoubleQuotedString(out, EncodeBase64(binary.data(), binary.size()), false);
			return true;
		}
	}
}